package algorithms;

import structure.DataPoint;
import structure.State;
import structure.Variable;
import utilities.*;

/**
 * Class to calculate the joint probability matrix between 2 variables and from
 * there the mutual entropy value.
 * @author Richard
 */

public class MutualEntropy {
         private static double log2( double x ){
             // Math.log is base e, natural log, ln
             return Math.log( x ) / Math.log( 2 );
         }

         public static double calculate(Variable a, Variable b){
             int numberOfStatesA = a.getNumberOfStates();  //The number of states for variable A
             int numberOfStatesB = b.getNumberOfStates();  //The number of states for variable B
             double divisor = (double)StringParse.data.getNumberOfDataPoints();  //The value of the divisor (equal to the number of DPs)
             double total = 0;  // The current total value of the sum of dependencies
             double temp = 0;
             double temp2 = 0;
             double[][] cooccurance = new double[numberOfStatesA][numberOfStatesB];  //The current cooccurance matrix
             double[] probA = new double[numberOfStatesA];  //The current discrete probability distribution of A
             double[] probB = new double[numberOfStatesB];  //The current discrete probability distribution of B

             /**
              * The following sequence of code calculates the joint probability
              * matrix between 2 variables.
              */

             //Iterate through all of the states in variable A
             for(int i=1; i<numberOfStatesA+1; i++){
                 //Iterate through all of the states in variable B
                 for(int j=1; j<numberOfStatesB+1; j++){
                     //Counts the number of times where the two variables with the two states occur
                     double and1and2 = L1Metric.compareStates(a,b,a.getStateById(i),b.getStateById(j));
                     //Works out the probabilty of the given combination happening
                     cooccurance[i-1][j-1] = and1and2/divisor;
                     System.out.println("Joint probability matrix:[" + i +"][" + j + "] = " + cooccurance[i-1][j-1]);
                 }
             }

             /**
              * Matrix calculated, now calculate probabilities for each variable and state.
              */

            //Iterate through all of the states in variable A
            for(int i=0; i<numberOfStatesA; i++){
                //Iterate through all of the states in variable B
                for(int j=0; j<numberOfStatesB; j++){
                    //Works out the probability distribution iteratively wy adding the jth member of the cooccurance array
                    probA[i] = probA[i] + cooccurance[i][j];
                }
            System.out.println("Prob A:[" + i +"] = " + probA[i]);
            }

            //Iterate through all of the states in variable B
            for(int i=0; i<numberOfStatesB; i++){
                //Iterate through all of the states in variable A
                for(int j=0; j<numberOfStatesA; j++){
                    //Works out the probability distribution iteratively wy adding the jth member of the cooccurance array
                    probB[i] = probB[i] + cooccurance[j][i];
                }
            System.out.println("Prob B:[" + i +"] = " + probB[i]);
            }

            //Iterate through all of the states in variable A
            for(int i=0; i<numberOfStatesA; i++){
                //Iterate through all of the states in variable B
                for(int j=0; j<numberOfStatesB; j++){
                    //Adds the next partial L1 metric to the current total of L1 metrics
                    if (probA[i]*probB[j] == 0){
                        temp2 = 0;
                    }
                    else temp2 = cooccurance[i][j]/(probA[i]*probB[j]);
                    
                    if (temp2 != 0){
                        temp = cooccurance[i][j]*(log2(temp2));
                    }
                    else temp = 0;
                    //Messy hack, since log function keeps returning NaN
                    total = total + temp;
                    
                    /* ERROR TESTING
                    System.out.println("COOCCURANCE IS " + cooccurance[i][j]);
                    System.out.println("PROB(A) IS " + probA[i]);
                    System.out.println("PROB(B) IS " + probB[j]);
                    System.out.println("TOTAL IS " + total);
                     */
                }
            }
            System.out.println("Total: " + total);
            return total;
         }


}
